package abc.handler;

import abc.utils.JacksonUtil;
import abc.utils.ReflectionUtil;
import abc.utils.StringUtil;
import abc.utils.ThreadUtil;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.Scope;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class RefreshScope implements Scope, BeanFactoryAware {

    private final String BASE_PACKAGE = "abc";
    private final String RESOURCE_PATTERN = "/**/*.class";

    private final static ConcurrentHashMap<String, Object> concurrentHashMap = new ConcurrentHashMap();

    private BeanFactory beanFactory;

    @PostConstruct
    void init() {
        //spring工具类，可以获取指定路径下的全部类
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        try {
            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + RESOURCE_PATTERN;
            Resource[] resources = resourcePatternResolver.getResources(pattern);
            //MetadataReader 的工厂类
            MetadataReaderFactory readerfactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
                //用于读取类信息
                MetadataReader reader = readerfactory.getMetadataReader(resource);
                //扫描到的class
                String classname = reader.getClassMetadata().getClassName();
                try {
                    Class<?> clazz = Class.forName(classname);
                    //判断是否有指定主解
                    org.springframework.cloud.context.config.annotation.RefreshScope anno1 = clazz.getAnnotation(org.springframework.cloud.context.config.annotation.RefreshScope.class);
                    ConfigurationProperties anno2 = clazz.getAnnotation(ConfigurationProperties.class);
                    if (anno1 != null && anno2 != null) {
                        String name = String.format("scopedTarget.%s", StringUtil.getHumpName(clazz.getSimpleName(), true));
                        //将注解中的类型值作为key，对应的类作为 value
                        synchronized (name) {
                            Object bean = beanFactory.getBean(clazz);
                            bean = getObjectByProxy(bean);
                            concurrentHashMap.put(name, bean);
                        }
                    }
                }
                catch (Exception ex) {
                    log.error(ex.getMessage());
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }


    public Object getObjectByProxy(Object proxy) throws Exception {
        //判断是否是代理对象
        if(AopUtils.isAopProxy(proxy)){
            //cglib 代理
            if(AopUtils.isCglibProxy(proxy)){
                //通过暴力反射拿到代理对象的拦截器属性，从拦截器获取目标对象
                Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
                h.setAccessible(true);
                Object dynamicAdvisedInterceptor = h.get(proxy);

                Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
                advised.setAccessible(true);
                Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
                //返回目标对象
                return target;
            }
            //jdk代理
            if(AopUtils.isJdkDynamicProxy(proxy)){
                //通过暴力反射拿到代理对象的拦截器属性，从拦截器获取目标对象
                Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
                h.setAccessible(true);
                AopProxy aopProxy = (AopProxy) h.get(proxy);

                Field advised = aopProxy.getClass().getDeclaredField("advised");
                advised.setAccessible(true);

                Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();

                return target;
            }
        }
        return null;
    }

    @Autowired
    ApplicationContext applicationContext;

    public static void refresh(Properties configProperties) {
        concurrentHashMap.keySet().stream().forEach(key-> {
            Class<?> type = concurrentHashMap.get(key).getClass();
            var annConfigProperties = type.getAnnotation(ConfigurationProperties.class);
            if(annConfigProperties==null || (StringUtil.isEmpty(annConfigProperties.value()) && StringUtil.isEmpty(annConfigProperties.prefix()))) {
                concurrentHashMap.remove(key);
                return;
            }
            String prefix = annConfigProperties.prefix();
            if(StringUtil.isEmpty(prefix)) {
                prefix = annConfigProperties.value();
            }
            for (Field fld : concurrentHashMap.get(key).getClass().getDeclaredFields()) {
                String propertyKey = String.format("%s.%s", prefix, StringUtil.getHumpName(fld.getName(), true));
                if(configProperties.containsKey(propertyKey)) {
                    try {
                        if(fld.getType().getSimpleName().endsWith("List")) {
                            Class<?> clazz = (Class)((ParameterizedType)ResolvableType.forField(fld).getType()).getActualTypeArguments()[0];
                            ReflectionUtil.setFieldValue(concurrentHashMap.get(key), fld.getName(), JacksonUtil.toList(configProperties.getProperty(propertyKey), clazz));
                        }
                        else {
                            ReflectionUtil.setFieldValue(concurrentHashMap.get(key), fld.getName(), JacksonUtil.parse(configProperties.getProperty(propertyKey), fld.getType()));
                        }
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    }
                }
                else {
                    concurrentHashMap.remove(key);
                }
            }
        });
    }

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        if(concurrentHashMap.containsKey(name)) {
            return concurrentHashMap.get(name);
        }
        else {
            synchronized (name) {
                Object object = objectFactory.getObject();
                concurrentHashMap.put(name, object);
                return object;
            }
        }
    }

    @Override
    public Object remove(String name) {
        Object object = null;
        if(concurrentHashMap.containsKey(name)) {
            object = concurrentHashMap.get(name);
            concurrentHashMap.remove(name);
        }
        return object;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        if(!concurrentHashMap.containsKey(name)) {
            ThreadUtil.onec(callback);
        }
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return null;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
